﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;

using System.Threading;
using System.Threading.Tasks;

using Winz.JsonModel;


using System.Xml.Serialization;
using System.IO;


namespace Winz.FoeBot
{
    public enum BarsType
    {
        Friends,
        Neighbour,
        Guild
    }


    public enum MotivationOrdersType
    {
        PO_MO_MM,
        PO_MM_MO,
        MO_PO_MM,
        MO_MM_PO,
        MM_PO_MO,
        MM_MO_PO,
    }


    [XmlRoot("Preference")]
    public class MotivationPreference
    {
        [XmlAttribute(AttributeName = "PlayerId")]
        public int player_id { get; set; }
        [XmlAttributeAttribute(AttributeName = "MotvationOrder")]
        public MotivationOrdersType orderPreference { get; set; }


        public MotivationPreference()
        {
        }
        public MotivationPreference(int playerId, MotivationOrdersType orderPreference)
        {
            this.player_id = playerId;
            this.orderPreference = orderPreference;
        }

    }

    public class MotvivationPreferences
    {
        [XmlElement("Preferences")]
        public List<MotivationPreference> Preferences { get; set; }

        public MotvivationPreferences()
        {
            Preferences = new List<MotivationPreference>();
        }

        public static MotvivationPreferences Load(string path)
        {
            var ser = new XmlSerializer(typeof(MotvivationPreferences));
            using (TextReader reader = new StreamReader(path))
            {

                return (MotvivationPreferences)ser.Deserialize(reader);
            }
        }
    }



    public class Motivator
    {

        public event EventHandler MotivatorPaused;
        public event EventHandler MotivatorStopped;
        public event EventHandler MotivatorStarted;
        public event PlayerNeedMotivationOrderEventHandler PlayerNeedMotivationOrder;
        

        public List<MotivationPreference> motivationPreferences;


        public delegate int clickDelegate(Point p);
        public delegate int clickBuidingDelegate(Entity e);

        public Action<Point> Click;
        public Action<Entity> ClickBuilding;
        public Action VisitAllTabs;

        public static List<MotivatorPlayer> socialBarplayers;
        public static MotivatorPlayer currentPlayer = null;
        public static MotivatorPlayer lastPlayer = null;

        List<BarsType> tabsToMotivate;

        public Bot.LogHandler Log;

        object locker = new object();


        Task TaskVisit;
        CancellationTokenSource tokenSource = new CancellationTokenSource();
        CancellationToken ct;

        public BotStateEnum motivatorState;


        public static Dictionary<MotivationOrdersType, List<MotivateType>> MotivationOrders = new Dictionary<MotivationOrdersType, List<MotivateType>> {
         {MotivationOrdersType.PO_MO_MM,new List<MotivateType> { MotivateType.PO, MotivateType.MO, MotivateType.MM}  },
         {MotivationOrdersType.PO_MM_MO,new List<MotivateType> { MotivateType.PO, MotivateType.MM, MotivateType.MO}  },
         {MotivationOrdersType.MO_PO_MM,new List<MotivateType> { MotivateType.MO, MotivateType.PO, MotivateType.MM}  },
         {MotivationOrdersType.MO_MM_PO,new List<MotivateType> { MotivateType.MO, MotivateType.MM, MotivateType.PO}  },
         {MotivationOrdersType.MM_PO_MO,new List<MotivateType> { MotivateType.MM, MotivateType.PO, MotivateType.MO}  },
         {MotivationOrdersType.MM_MO_PO,new List<MotivateType> { MotivateType.MM, MotivateType.MO, MotivateType.PO}  }
        };

        public static List<MotivateType> GetListFromMotivationOrderType(MotivationOrdersType t)
        {
            return MotivationOrders.Single(mo => mo.Key == t).Value;
        }

        public Motivator(Action<Point> click, Action<Entity> clickBuilding)
        {
            this.motivatorState = BotStateEnum.Stopped;
            this.Click = click;
            this.ClickBuilding = clickBuilding;
            this.motivationPreferences = new List<MotivationPreference>();


            socialBarplayers = new List<MotivatorPlayer>();
            this.tabsToMotivate = new List<BarsType>();

            this.Log = new Bot.LogHandler((string p, object[] arg) => { });


            tokenSource = new CancellationTokenSource();
            ct = tokenSource.Token;

            VisitAllTabs = new Action(() =>
            {
                Thread.Sleep(500);
                Click(Bot.settings.socialNeighbours);
                Thread.Sleep(1000);
                Click(Bot.settings.socialGuild);
                Thread.Sleep(1000);
                Click(Bot.settings.socialFriends);
                Thread.Sleep(1000);
            });




            Init();
        }

        public void Init()
        {
            //            motivationPreferences.Add(new MotivationPreference("toto", "momomomo"));
            //            motivationPreferences.Save(Environment.SpecialFolder.LocalApplicationData + @"WinzFoeBot\motivationPreferences.xml");

            motivationPreferences = motivationPreferences.Load();



            this.tabsToMotivate = new List<BarsType>();
            if (Bot.settings.visitFriends)
                tabsToMotivate.Add(BarsType.Friends);
            if (Bot.settings.visitGuild)
                tabsToMotivate.Add(BarsType.Guild);
            if (Bot.settings.visitNeighboors)
                tabsToMotivate.Add(BarsType.Neighbour);

        }




        public void Start()
        {
            if (motivatorState != BotStateEnum.Paused)
                Init();

            motivatorState = BotStateEnum.Started;
            MotivatorStarted(this, new EventArgs());

            
            VisitAllTabs.BeginInvoke(new AsyncCallback((IAsyncResult Res) => { VisitNext(500); }),null);

            
        }

        public void Stop()
        {
            motivatorState = BotStateEnum.Stopped;
            MotivatorStopped(this, new EventArgs());
        }

        public void Pause()
        {
            motivatorState = BotStateEnum.Paused;
            MotivatorPaused(this, new EventArgs());
        }





        public void OnPlayerVisited(object sender, MessageEventArgs<OtherPlayeCity> e)
        {
            Log("{0} visited (Thread : {1} {2})", e.Message.OtherPlayer.Name, Thread.CurrentThread.Name, Thread.CurrentThread.ManagedThreadId);

            Remove(e.Message.OtherPlayer);
            Thread.Sleep(1000);

            lastPlayer = currentPlayer;
            if (lastPlayer != null
                && socialBarplayers.Exists(sp => sp.bar == lastPlayer.bar
                                            && sp.player.PlayerId == e.Message.OtherPlayer.PlayerId))
            {
                currentPlayer = socialBarplayers.Single(sp => sp.bar == lastPlayer.bar && sp.player.PlayerId == e.Message.OtherPlayer.PlayerId);
            }
            else
                currentPlayer = socialBarplayers.First(sp => sp.player.PlayerId == e.Message.OtherPlayer.PlayerId);



            if (this.motivatorState != BotStateEnum.Started)
                return;


            MotivationOrdersType t = getMotivationOrderFromPlayer(e.Message.OtherPlayer);
            List<MotivateType> order;
            Motivator.MotivationOrders.TryGetValue(t, out order);
            OtherPlayeCity city = e.Message;


            if (isMotivationOrderSet(city.OtherPlayer))
            {
                Motivate(city, order);
            }
            else
            {
                if (Bot.settings.waitForUserSelectMotivation)
                {
                    Log("Waiting for user to select a motivation order ( and motivate), the UI should take take of thet");

                    PlayerNeedMotivationOrder(this,new MessageEventArgs<OtherPlayeCity>(city));

                    /*                    if (Bot.settings.useDefaultIfNotSet)
                                        {
                                            MotivationPrefTimoutHandler = new System.Timers.ElapsedEventHandler((object o, System.Timers.ElapsedEventArgs arg) =>
                                            {
                                                lock (locker)
                                                {
                                                    Log("Timout : motivating player");
                                                    Motivate(city, order);
                                                }
                                            });
                                        }
                                        else
                                        {
                                            MotivationPrefTimoutHandler = new System.Timers.ElapsedEventHandler((object o, System.Timers.ElapsedEventArgs arg) =>
                                            {
                                                lock (locker)
                                                {
                                                    Log("Timout : switching player");
                                                    VisitNext(1000);
                                                }
                                            });
                                        }

                                        MotivationPrefTimeout.Start();
                    */
                }
                else
                {
                    if (Bot.settings.useDefaultIfNotSet)
                    {
                        Motivate(city, order);
                    }
                    else
                    {
                        VisitNext(1000);
                    }
                }
            }

        }


        public void OnPlayerMotivated(object sender, MessageEventArgs<OtherPlayer> e)
        {
            Log("{0} motivated (Thread : {1} {2})", e.Message.Name, Thread.CurrentThread.Name, Thread.CurrentThread.ManagedThreadId);

            Remove(e.Message);

            if (motivatorState != BotStateEnum.Started)
                return;

            VisitNext(1000);
        }


        public void OnPlayerListRecieved(object sender, MessageEventArgs<List<OtherPlayer>> e)
        {
            Log("PlayerList recieved");
            UpdatePlayers(e.Message);

            if (motivatorState == BotStateEnum.Started)
                VisitNext(1000);
        }


        /// <summary>
        /// Choose the next player, and call the VisitPlayer if needed
        /// </summary>
        /// <param name="delay"></param>
        public void VisitNext(int delay = 0)
        {
            
            MotivatorPlayer next = null;
            // Try to get the first player of the same tab
            if (currentPlayer != null)
                next = socialBarplayers
                        .OrderByDescending(sp => sp.player.Score)
                        .FirstOrDefault(sp =>
                            sp.bar == currentPlayer.bar
                            && sp.needMotivation()
                            && sp.barRank > currentPlayer.barRank
                            );

            // If not found get the next player of any tab
            if (next == null)
                next = socialBarplayers
                    .OrderByDescending(sp => sp.player.Score)
                    .FirstOrDefault(sp => sp.needMotivation());

            if(currentPlayer != null)
                Remove(currentPlayer.player);
            // If none we are done
            if (next == null)
            {
                Log("Neigbor : {0}/{1}", socialBarplayers.Where(sp => sp.bar == BarsType.Neighbour && sp.needMotivation()).Count(), socialBarplayers.Where(sp => sp.bar == BarsType.Neighbour).Count());
                Log("No Player left found, stopping");
                this.Stop();
                return;
            }

            
            Log("Current : {0}", currentPlayer != null ? currentPlayer.player.Name : "Not set");
            Log("Visiting {0}, bar:{1}, rank:{2} (Thread : {3} {4})", next.player.Name, next.bar, next.barRank, Thread.CurrentThread.Name, Thread.CurrentThread.ManagedThreadId);
            VisitPlayer(next, delay);
        }


        /// <summary>
        /// Motivate the city. It chooses the best building, and send the clicks
        /// </summary>
        /// <param name="city">The OtherPLayerCity to be motivated. Should fir with the current city in the browser</param>
        /// <param name="order">The Order to use</param>
        /// <param name="delay"></param>
        public void Motivate(OtherPlayeCity city, List<MotivateType> order, int delay = 500)
        {
            if (city.OtherPlayer.NextInteractionIn != 0)
            {
                Log("On action cooldown");
                Remove(city.OtherPlayer);
                VisitNext(1000);
            }
            MotivateType type;
            MotivatorPlayer sbp = GetSocioalBarParPlayer(city.OtherPlayer);
            Entity building = sbp.getBuidingToMotivate(city, order, out type);

            if (building == null)
            {
                Log("No Buiding found");
                Remove(city.OtherPlayer);
                VisitNext(1000);
            }
            else
            {

                Action ActionMotivate = () =>
                {
                    lock (socialBarplayers)
                    {
                        Point btn;
                        int trys = 4;
                        if (type == MotivateType.PO)
                            btn = Bot.settings.polishBtn;
                        else
                            btn = Bot.settings.motivateBtn;

                        Click(btn);
                        Thread.Sleep(200);
                        for (int i = 0; i < trys; i++)
                        {
                            Log("Try {1} (Thread : {0})", Thread.CurrentThread.ManagedThreadId, i + 1);
                            ClickBuilding(building);
                            Thread.Sleep(500);
                        }
                    }
                };


                tokenSource.Cancel(true);


                tokenSource = new CancellationTokenSource();
                ct = tokenSource.Token;
                TaskVisit = new Task(ActionMotivate, ct);
                TaskVisit.Start();
            }
        }



        public MotivateType getBuidingMotivationType(Entity building)
        {

            switch (Bot.cityEntities.Single(ce => ce.Id == building.CityentityId).Type)
            {
                case "residential":
                    return MotivateType.MO;
                case "culture":
                    return MotivateType.PO;
                case "production":
                    return MotivateType.MM;

                default:
                    return MotivateType.PO;
            };

        }




        public MotivatorPlayer GetSocioalBarParPlayer(OtherPlayer player)
        {
            return socialBarplayers.FirstOrDefault(p => p.player.PlayerId == player.PlayerId);
        }


        public bool isMotivationOrderSet(OtherPlayer player)
        {
            MotivationPreference p = motivationPreferences.FirstOrDefault((mp) => { return mp.player_id == player.PlayerId; });
            if (p == null)
                return false;
            else
                return true;
        }

        public MotivationOrdersType getMotivationOrderFromPlayer(OtherPlayer player, MotivationOrdersType defaultOrder = MotivationOrdersType.PO_MM_MO)
        {
            MotivationPreference p = motivationPreferences.FirstOrDefault((mp) => { return mp.player_id == player.PlayerId; });
            if (p == null)
                return defaultOrder;
            else
                return p.orderPreference;
        }

        private void AddPlayers(List<OtherPlayer> players)
        {
            int i = 0;
            i = 0;
            players
                .OrderByDescending(p => p.Score).ToList()
                .ForEach(p =>
                {

                    BarsType b;
                    switch (p.Class)
                    {
                        case "Player":
                            b = BarsType.Neighbour;
                            break;
                        case "ClanMember":
                            b = BarsType.Guild;
                            break;
                        default:
                            b = BarsType.Friends;
                            break;
                    }
                    socialBarplayers.Add(new MotivatorPlayer(p, i++, b));
                });

        }




        public void UpdatePlayers(List<OtherPlayer> list)
        {
            if (list.Select(p => p.Class).Distinct().Count() != 1)
            {
                Log("Several Tabs in the list");
                return;
            }

            socialBarplayers.RemoveAll(sbp => { return list.Select(p => p.Class).Distinct().Contains(sbp.player.Class); });
            AddPlayers(list);

        }




        public void VisitPlayer(MotivatorPlayer sPlayer, int delay)
        {
            Thread.Sleep(delay);

            Action ActionVisit = () =>
            {
                lock (socialBarplayers)
                {

                    OtherPlayer player = sPlayer.player;

                    int pos = GetPos(sPlayer);
                    int barCount = socialBarplayers.Where(p => { return p.player.Class == sPlayer.player.Class; }).Count();

                    Thread.Sleep(500);
                    Click(getTabPoint(sPlayer.bar));
                    Thread.Sleep(300);

                    if (lastPlayer != null && currentPlayer.bar == sPlayer.bar)
                    {
                        if (sPlayer.barRank >= (barCount - 5) && barCount > 5)
                        {
                            ClickLast();
                            Thread.Sleep(500);
                        }

                        if (GetPos5(sPlayer) > GetPos5(currentPlayer))
                        {
                            for (int i = 0; i < GetPos5(sPlayer) - GetPos5(currentPlayer); i++)
                            {
                                ClickNext();
                                Thread.Sleep(500);
                            }
                        }
                        else if (GetPos5(sPlayer) < GetPos5(currentPlayer))
                        {
                            for (int i = 0; i < GetPos5(currentPlayer) - GetPos5(sPlayer); i++)
                            {
                                ClickPrevious();
                                Thread.Sleep(500);
                            }
                        }

                    }
                    else
                    {
                        ClickFirst();
                        Thread.Sleep(1500);

                        for (int i = 0; i < GetPos5(sPlayer); i++)
                        {
                            ClickNext();
                            Thread.Sleep(500);
                        }
                    }

                    Log("Click Pos {0} , rank {1} (T : {2} {3})", pos, sPlayer.barRank, Thread.CurrentThread.Name, Thread.CurrentThread.ManagedThreadId);
                    ClickPlayer(pos);

                    if (sPlayer.barRank == barCount - 1)
                    {
                        Log("End of the tab reached");
                        tabsToMotivate.Remove(sPlayer.bar);
                    }
                }
            };


            tokenSource.Cancel(true);


            tokenSource = new CancellationTokenSource();
            ct = tokenSource.Token;
            TaskVisit = new Task(ActionVisit, ct);
            TaskVisit.Start();
        }

        #region clicks
        public void ClickNext()
        {
            Click(Bot.settings.socialNext);
        }

        public void ClickPrevious()
        {
            Click(Bot.settings.socialPrevious);
        }

        public void ClickFirst()
        {
            Click(Bot.settings.socialFirst);
            Thread.Sleep(10);
            Click(Bot.settings.socialFirst);

        }
        public void ClickLast()
        {
            Click(Bot.settings.socialLast);
            Thread.Sleep(10);
            Click(Bot.settings.socialLast);

        }


        public void ClickPlayer(int i)
        {
            Point p = new Point(Bot.settings.socialPlayerOrigin.X + Bot.settings.socialPlayerDelta.X * (i), Bot.settings.socialPlayerOrigin.Y);
            Click(p);
        }
        #endregion



        public void Remove(OtherPlayer p)
        {
            lock (socialBarplayers)
            {
                socialBarplayers.Where(sbp => sbp.player.PlayerId == p.PlayerId).ToList().
                    ForEach(sbp => sbp.player.NextInteractionIn = 999999);
            }
        }



        public Point getTabPoint(BarsType bar)
        {
            Point tabPoint;
            switch (bar)
            {
                case BarsType.Friends:
                    tabPoint = Bot.settings.socialFriends;
                    break;
                case BarsType.Guild:
                    tabPoint = Bot.settings.socialGuild;
                    break;
                case BarsType.Neighbour:
                    tabPoint = Bot.settings.socialNeighbours;
                    break;
                default:
                    tabPoint = new Point(0, 0);
                    break;
            }
            return tabPoint;

        }



        private int GetPos(MotivatorPlayer sp)
        {
            int barCount = socialBarplayers.Where(p => { return p.player.Class == sp.player.Class; }).Count();
            int pos = (sp.barRank % 5) + 1;
            if (sp.barRank >= (barCount - 5) && barCount > 5)
                pos = sp.barRank - (barCount - 5) + 1;
            return pos;
        }

        private int GetPos5(MotivatorPlayer sp)
        {
            return sp.barRank / 5;
        }

    }
}
